"""
Dependency resolver for feature flags in CodeMCP.

Handles feature flag dependencies, conflicts, and provides
dependency graph analysis and validation.
"""

from typing import Dict, List, Optional, Set, Tuple, Any
from dataclasses import dataclass, field
from enum import Enum
from collections import defaultdict, deque

from ...core.error_handler import CodeMCPError, log_info, log_warning, log_debug, handle_error


class DependencyType(Enum):
    """Types of dependencies between feature flags."""
    REQUIRED = "required"  # Flag A requires Flag B to be enabled
    CONFLICTS = "conflicts"  # Flag A conflicts with Flag B
    IMPLIES = "implies"  # Flag A being enabled implies Flag B should be enabled
    MUTUAL_EXCLUSION = "mutual_exclusion"  # Only one of the flags can be enabled


class DependencyResolution(Enum):
    """Dependency resolution strategies."""
    STRICT = "strict"  # Fail if dependencies cannot be satisfied
    BEST_EFFORT = "best_effort"  # Try to satisfy as many as possible
    IGNORE_CONFLICTS = "ignore_conflicts"  # Ignore conflict dependencies
    CASCADE_ENABLE = "cascade_enable"  # Auto-enable required dependencies
    CASCADE_DISABLE = "cascade_disable"  # Auto-disable conflicting flags


@dataclass
class DependencyRule:
    """Represents a dependency rule between feature flags."""
    source_flag: str
    target_flag: str
    dependency_type: DependencyType
    required: bool = True
    priority: int = 0
    metadata: Dict[str, Any] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert dependency rule to dictionary."""
        return {
            "source_flag": self.source_flag,
            "target_flag": self.target_flag,
            "dependency_type": self.dependency_type.value,
            "required": self.required,
            "priority": self.priority,
            "metadata": self.metadata
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "DependencyRule":
        """Create dependency rule from dictionary."""
        return cls(
            source_flag=data["source_flag"],
            target_flag=data["target_flag"],
            dependency_type=DependencyType(data["dependency_type"]),
            required=data.get("required", True),
            priority=data.get("priority", 0),
            metadata=data.get("metadata", {})
        )


@dataclass
class DependencyViolation:
    """Represents a dependency violation."""
    rule: DependencyRule
    violation_type: str
    description: str
    severity: str = "error"  # error, warning, info
    suggested_actions: List[str] = field(default_factory=list)


@dataclass
class DependencyGraph:
    """Represents the feature flag dependency graph."""
    nodes: Set[str] = field(default_factory=set)
    edges: Dict[str, List[DependencyRule]] = field(default_factory=lambda: defaultdict(list))
    reverse_edges: Dict[str, List[DependencyRule]] = field(default_factory=lambda: defaultdict(list))
    
    def add_rule(self, rule: DependencyRule):
        """Add a dependency rule to the graph."""
        self.nodes.add(rule.source_flag)
        self.nodes.add(rule.target_flag)
        self.edges[rule.source_flag].append(rule)
        self.reverse_edges[rule.target_flag].append(rule)
    
    def remove_rule(self, rule: DependencyRule):
        """Remove a dependency rule from the graph."""
        if rule in self.edges[rule.source_flag]:
            self.edges[rule.source_flag].remove(rule)
        if rule in self.reverse_edges[rule.target_flag]:
            self.reverse_edges[rule.target_flag].remove(rule)
    
    def get_dependencies(self, flag_name: str) -> List[DependencyRule]:
        """Get all dependencies for a flag (outgoing edges)."""
        return self.edges.get(flag_name, [])
    
    def get_dependents(self, flag_name: str) -> List[DependencyRule]:
        """Get all dependents of a flag (incoming edges)."""
        return self.reverse_edges.get(flag_name, [])


class DependencyError(CodeMCPError):
    """Dependency resolution specific errors."""
    
    def __init__(self, message: str, violations: List[DependencyViolation] = None, **kwargs):
        super().__init__(message, "DEPENDENCY_ERROR", **kwargs)
        if violations:
            self.details["violations"] = [v.__dict__ for v in violations]


class DependencyResolver:
    """
    Dependency resolver for feature flags.
    
    Handles feature flag dependencies, conflicts, and provides
    dependency graph analysis and validation capabilities.
    """
    
    def __init__(self):
        # Dependency graph
        self.dependency_graph = DependencyGraph()
        
        # Resolver configuration
        self.resolution_strategy = DependencyResolution.STRICT
        self.max_cascade_depth = 10
        self.enable_cycle_detection = True
        
        # Statistics
        self.resolution_stats = {
            "total_resolutions": 0,
            "violations_found": 0,
            "cycles_detected": 0,
            "cascades_performed": 0
        }
        
        log_info("Dependency resolver initialized")
    
    async def initialize(self):
        """Initialize the dependency resolver."""
        try:
            # Load default dependency rules
            await self._load_default_dependencies()
            
            log_info("Dependency resolver initialized successfully")
            
        except Exception as e:
            error_response = handle_error(e, {"operation": "dependency_resolver_init"})
            raise DependencyError(f"Failed to initialize dependency resolver: {error_response['message']}")
    
    async def _load_default_dependencies(self):
        """Load default dependency rules for CodeMCP GSI features."""
        try:
            default_rules = [
                # Real-time wiki updates requires enhanced code analysis
                DependencyRule(
                    source_flag="real_time_wiki_updates",
                    target_flag="enhanced_code_analysis",
                    dependency_type=DependencyType.REQUIRED,
                    priority=10
                ),
                
                # ML code suggestions conflicts with enhanced code analysis
                DependencyRule(
                    source_flag="ml_code_suggestions",
                    target_flag="enhanced_code_analysis",
                    dependency_type=DependencyType.CONFLICTS,
                    priority=5
                ),
                
                # Advanced caching implies enhanced code analysis for optimal performance
                DependencyRule(
                    source_flag="advanced_caching",
                    target_flag="enhanced_code_analysis",
                    dependency_type=DependencyType.IMPLIES,
                    required=False,
                    priority=3
                ),
                
                # GSI DRP-4DVar integration requires enhanced code analysis
                DependencyRule(
                    source_flag="gsi_drp4dvar_integration",
                    target_flag="enhanced_code_analysis",
                    dependency_type=DependencyType.REQUIRED,
                    priority=8
                )
            ]
            
            for rule in default_rules:
                await self.add_dependency_rule(rule)
            
            log_info(f"Loaded {len(default_rules)} default dependency rules")
            
        except Exception as e:
            log_warning(f"Failed to load default dependencies: {e}")
    
    async def add_dependency_rule(self, rule: DependencyRule) -> bool:
        """Add a dependency rule to the resolver."""
        try:
            # Check for cycles if enabled
            if self.enable_cycle_detection:
                if await self._would_create_cycle(rule):
                    raise DependencyError(
                        f"Adding rule would create a dependency cycle: {rule.source_flag} -> {rule.target_flag}"
                    )
            
            self.dependency_graph.add_rule(rule)
            log_debug(f"Added dependency rule: {rule.source_flag} {rule.dependency_type.value} {rule.target_flag}")
            
            return True
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "add_dependency_rule",
                "source_flag": rule.source_flag,
                "target_flag": rule.target_flag,
                "dependency_type": rule.dependency_type.value
            })
            raise DependencyError(f"Failed to add dependency rule: {error_response['message']}")
    
    async def remove_dependency_rule(self, rule: DependencyRule) -> bool:
        """Remove a dependency rule from the resolver."""
        try:
            self.dependency_graph.remove_rule(rule)
            log_debug(f"Removed dependency rule: {rule.source_flag} {rule.dependency_type.value} {rule.target_flag}")
            
            return True
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "remove_dependency_rule",
                "source_flag": rule.source_flag,
                "target_flag": rule.target_flag
            })
            raise DependencyError(f"Failed to remove dependency rule: {error_response['message']}")
    
    async def validate_flag_states(self, flag_states: Dict[str, bool]) -> List[DependencyViolation]:
        """Validate feature flag states against dependency rules."""
        try:
            violations = []
            self.resolution_stats["total_resolutions"] += 1
            
            # Check each flag's dependencies
            for flag_name, is_enabled in flag_states.items():
                if not is_enabled:
                    continue  # Skip disabled flags for most checks
                
                # Get dependencies for this flag
                dependencies = self.dependency_graph.get_dependencies(flag_name)
                
                for rule in dependencies:
                    violation = await self._check_dependency_rule(rule, flag_states)
                    if violation:
                        violations.append(violation)
            
            # Check for mutual exclusions
            mutual_exclusion_violations = await self._check_mutual_exclusions(flag_states)
            violations.extend(mutual_exclusion_violations)
            
            self.resolution_stats["violations_found"] += len(violations)
            
            return violations
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "validate_flag_states",
                "flag_states": flag_states
            })
            raise DependencyError(f"Failed to validate flag states: {error_response['message']}")
    
    async def _check_dependency_rule(self, rule: DependencyRule, flag_states: Dict[str, bool]) -> Optional[DependencyViolation]:
        """Check a single dependency rule against flag states."""
        try:
            source_enabled = flag_states.get(rule.source_flag, False)
            target_enabled = flag_states.get(rule.target_flag, False)
            
            if rule.dependency_type == DependencyType.REQUIRED:
                if source_enabled and not target_enabled:
                    return DependencyViolation(
                        rule=rule,
                        violation_type="missing_required_dependency",
                        description=f"Flag '{rule.source_flag}' requires '{rule.target_flag}' to be enabled",
                        severity="error" if rule.required else "warning",
                        suggested_actions=[f"Enable '{rule.target_flag}' flag"]
                    )
            
            elif rule.dependency_type == DependencyType.CONFLICTS:
                if source_enabled and target_enabled:
                    return DependencyViolation(
                        rule=rule,
                        violation_type="conflicting_flags",
                        description=f"Flag '{rule.source_flag}' conflicts with '{rule.target_flag}'",
                        severity="error" if rule.required else "warning",
                        suggested_actions=[
                            f"Disable '{rule.source_flag}' flag",
                            f"Disable '{rule.target_flag}' flag"
                        ]
                    )
            
            elif rule.dependency_type == DependencyType.IMPLIES:
                if source_enabled and not target_enabled:
                    return DependencyViolation(
                        rule=rule,
                        violation_type="missing_implied_dependency",
                        description=f"Flag '{rule.source_flag}' implies '{rule.target_flag}' should be enabled",
                        severity="warning",
                        suggested_actions=[f"Consider enabling '{rule.target_flag}' flag"]
                    )
            
            return None
            
        except Exception as e:
            log_warning(f"Failed to check dependency rule: {e}")
            return None
    
    async def _check_mutual_exclusions(self, flag_states: Dict[str, bool]) -> List[DependencyViolation]:
        """Check for mutual exclusion violations."""
        violations = []
        
        try:
            # Find mutual exclusion groups
            mutual_exclusion_rules = [
                rule for flag_rules in self.dependency_graph.edges.values()
                for rule in flag_rules
                if rule.dependency_type == DependencyType.MUTUAL_EXCLUSION
            ]
            
            # Group by mutual exclusion sets
            exclusion_groups = defaultdict(set)
            for rule in mutual_exclusion_rules:
                group_key = tuple(sorted([rule.source_flag, rule.target_flag]))
                exclusion_groups[group_key].add(rule.source_flag)
                exclusion_groups[group_key].add(rule.target_flag)
            
            # Check each group
            for group_flags in exclusion_groups.values():
                enabled_flags = [flag for flag in group_flags if flag_states.get(flag, False)]
                
                if len(enabled_flags) > 1:
                    # Find the rule that defines this mutual exclusion
                    rule = next(
                        (r for r in mutual_exclusion_rules 
                         if r.source_flag in group_flags and r.target_flag in group_flags),
                        None
                    )
                    
                    if rule:
                        violations.append(DependencyViolation(
                            rule=rule,
                            violation_type="mutual_exclusion_violation",
                            description=f"Mutual exclusion violated: {', '.join(enabled_flags)} cannot be enabled together",
                            severity="error",
                            suggested_actions=[
                                f"Disable all but one of: {', '.join(enabled_flags)}"
                            ]
                        ))
            
            return violations
            
        except Exception as e:
            log_warning(f"Failed to check mutual exclusions: {e}")
            return []
    
    async def resolve_dependencies(self, 
                                 target_flag_states: Dict[str, bool],
                                 strategy: DependencyResolution = None) -> Dict[str, bool]:
        """Resolve dependencies and return corrected flag states."""
        try:
            strategy = strategy or self.resolution_strategy
            resolved_states = target_flag_states.copy()
            
            if strategy == DependencyResolution.STRICT:
                # Validate and fail if violations exist
                violations = await self.validate_flag_states(resolved_states)
                error_violations = [v for v in violations if v.severity == "error"]
                if error_violations:
                    raise DependencyError(
                        f"Dependency violations found in strict mode: {len(error_violations)} errors",
                        violations=error_violations
                    )
            
            elif strategy == DependencyResolution.CASCADE_ENABLE:
                resolved_states = await self._cascade_enable_dependencies(resolved_states)
            
            elif strategy == DependencyResolution.CASCADE_DISABLE:
                resolved_states = await self._cascade_disable_conflicts(resolved_states)
            
            elif strategy == DependencyResolution.BEST_EFFORT:
                resolved_states = await self._best_effort_resolution(resolved_states)
            
            elif strategy == DependencyResolution.IGNORE_CONFLICTS:
                resolved_states = await self._ignore_conflicts_resolution(resolved_states)
            
            self.resolution_stats["cascades_performed"] += 1
            
            return resolved_states
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "resolve_dependencies",
                "target_states": target_flag_states,
                "strategy": strategy.value if strategy else None
            })
            raise DependencyError(f"Failed to resolve dependencies: {error_response['message']}")
    
    async def _cascade_enable_dependencies(self, flag_states: Dict[str, bool]) -> Dict[str, bool]:
        """Enable required dependencies in cascade fashion."""
        resolved_states = flag_states.copy()
        changes_made = True
        cascade_depth = 0
        
        while changes_made and cascade_depth < self.max_cascade_depth:
            changes_made = False
            cascade_depth += 1
            
            for flag_name, is_enabled in list(resolved_states.items()):
                if not is_enabled:
                    continue
                
                dependencies = self.dependency_graph.get_dependencies(flag_name)
                
                for rule in dependencies:
                    if rule.dependency_type == DependencyType.REQUIRED:
                        target_flag = rule.target_flag
                        if not resolved_states.get(target_flag, False):
                            resolved_states[target_flag] = True
                            changes_made = True
                            log_debug(f"Cascade enabled {target_flag} due to {flag_name}")
        
        return resolved_states
    
    async def _cascade_disable_conflicts(self, flag_states: Dict[str, bool]) -> Dict[str, bool]:
        """Disable conflicting flags in cascade fashion."""
        resolved_states = flag_states.copy()
        changes_made = True
        cascade_depth = 0
        
        while changes_made and cascade_depth < self.max_cascade_depth:
            changes_made = False
            cascade_depth += 1
            
            for flag_name, is_enabled in list(resolved_states.items()):
                if not is_enabled:
                    continue
                
                dependencies = self.dependency_graph.get_dependencies(flag_name)
                
                for rule in dependencies:
                    if rule.dependency_type == DependencyType.CONFLICTS:
                        target_flag = rule.target_flag
                        if resolved_states.get(target_flag, False):
                            # Disable the lower priority flag
                            if rule.priority > 0:
                                resolved_states[target_flag] = False
                                changes_made = True
                                log_debug(f"Cascade disabled {target_flag} due to conflict with {flag_name}")
        
        return resolved_states
    
    async def _best_effort_resolution(self, flag_states: Dict[str, bool]) -> Dict[str, bool]:
        """Best effort resolution - try to satisfy as many dependencies as possible."""
        resolved_states = flag_states.copy()
        
        # First, try to enable required dependencies
        resolved_states = await self._cascade_enable_dependencies(resolved_states)
        
        # Then, disable conflicts
        resolved_states = await self._cascade_disable_conflicts(resolved_states)
        
        return resolved_states
    
    async def _ignore_conflicts_resolution(self, flag_states: Dict[str, bool]) -> Dict[str, bool]:
        """Ignore conflict dependencies - only handle required dependencies."""
        resolved_states = flag_states.copy()
        
        # Only handle required dependencies
        changes_made = True
        cascade_depth = 0
        
        while changes_made and cascade_depth < self.max_cascade_depth:
            changes_made = False
            cascade_depth += 1
            
            for flag_name, is_enabled in list(resolved_states.items()):
                if not is_enabled:
                    continue
                
                dependencies = self.dependency_graph.get_dependencies(flag_name)
                
                for rule in dependencies:
                    if rule.dependency_type == DependencyType.REQUIRED:
                        target_flag = rule.target_flag
                        if not resolved_states.get(target_flag, False):
                            resolved_states[target_flag] = True
                            changes_made = True
        
        return resolved_states
    
    async def _would_create_cycle(self, new_rule: DependencyRule) -> bool:
        """Check if adding a rule would create a cycle in the dependency graph."""
        try:
            # Only check for cycles in dependency types that could create them
            if new_rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                return await self._has_path(new_rule.target_flag, new_rule.source_flag)
            
            return False
            
        except Exception as e:
            log_warning(f"Failed to check for cycles: {e}")
            return False
    
    async def _has_path(self, start_flag: str, end_flag: str) -> bool:
        """Check if there's a path from start_flag to end_flag in the dependency graph."""
        try:
            visited = set()
            queue = deque([start_flag])
            
            while queue:
                current_flag = queue.popleft()
                
                if current_flag == end_flag:
                    return True
                
                if current_flag in visited:
                    continue
                
                visited.add(current_flag)
                
                # Add all targets that this flag depends on
                dependencies = self.dependency_graph.get_dependencies(current_flag)
                for rule in dependencies:
                    if rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                        if rule.target_flag not in visited:
                            queue.append(rule.target_flag)
            
            return False
            
        except Exception as e:
            log_warning(f"Failed to check path: {e}")
            return False
    
    async def detect_cycles(self) -> List[List[str]]:
        """Detect all cycles in the dependency graph."""
        try:
            cycles = []
            visited = set()
            rec_stack = set()
            
            def dfs(node: str, path: List[str]) -> bool:
                if node in rec_stack:
                    # Found a cycle
                    cycle_start = path.index(node)
                    cycle = path[cycle_start:] + [node]
                    cycles.append(cycle)
                    return True
                
                if node in visited:
                    return False
                
                visited.add(node)
                rec_stack.add(node)
                
                # Visit all dependencies
                dependencies = self.dependency_graph.get_dependencies(node)
                for rule in dependencies:
                    if rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                        dfs(rule.target_flag, path + [node])
                
                rec_stack.remove(node)
                return False
            
            # Check all nodes
            for node in self.dependency_graph.nodes:
                if node not in visited:
                    dfs(node, [])
            
            self.resolution_stats["cycles_detected"] += len(cycles)
            
            return cycles
            
        except Exception as e:
            log_warning(f"Failed to detect cycles: {e}")
            return []
    
    async def get_dependency_tree(self, flag_name: str, max_depth: int = 5) -> Dict[str, Any]:
        """Get the dependency tree for a specific flag."""
        try:
            def build_tree(current_flag: str, depth: int) -> Dict[str, Any]:
                if depth >= max_depth:
                    return {"flag": current_flag, "max_depth_reached": True}
                
                tree = {"flag": current_flag, "dependencies": []}
                
                dependencies = self.dependency_graph.get_dependencies(current_flag)
                for rule in dependencies:
                    subtree = build_tree(rule.target_flag, depth + 1)
                    subtree["rule"] = rule.to_dict()
                    tree["dependencies"].append(subtree)
                
                return tree
            
            return build_tree(flag_name, 0)
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "get_dependency_tree",
                "flag_name": flag_name
            })
            raise DependencyError(f"Failed to get dependency tree: {error_response['message']}")
    
    async def get_impact_analysis(self, flag_name: str) -> Dict[str, Any]:
        """Analyze the impact of enabling/disabling a flag."""
        try:
            analysis = {
                "flag": flag_name,
                "direct_dependencies": [],
                "direct_dependents": [],
                "transitive_dependencies": set(),
                "transitive_dependents": set(),
                "potential_conflicts": [],
                "risk_level": "low"
            }
            
            # Get direct relationships
            dependencies = self.dependency_graph.get_dependencies(flag_name)
            dependents = self.dependency_graph.get_dependents(flag_name)
            
            analysis["direct_dependencies"] = [rule.to_dict() for rule in dependencies]
            analysis["direct_dependents"] = [rule.to_dict() for rule in dependents]
            
            # Find transitive dependencies
            visited = set()
            
            def find_transitive(current_flag: str, is_dependency: bool):
                if current_flag in visited or current_flag == flag_name:
                    return
                
                visited.add(current_flag)
                
                if is_dependency:
                    analysis["transitive_dependencies"].add(current_flag)
                    next_rules = self.dependency_graph.get_dependencies(current_flag)
                else:
                    analysis["transitive_dependents"].add(current_flag)
                    next_rules = self.dependency_graph.get_dependents(current_flag)
                
                for rule in next_rules:
                    if rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                        target = rule.target_flag if is_dependency else rule.source_flag
                        find_transitive(target, is_dependency)
            
            # Find transitive dependencies and dependents
            for rule in dependencies:
                if rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                    find_transitive(rule.target_flag, True)
            
            visited.clear()
            
            for rule in dependents:
                if rule.dependency_type in [DependencyType.REQUIRED, DependencyType.IMPLIES]:
                    find_transitive(rule.source_flag, False)
            
            # Convert sets to lists for JSON serialization
            analysis["transitive_dependencies"] = list(analysis["transitive_dependencies"])
            analysis["transitive_dependents"] = list(analysis["transitive_dependents"])
            
            # Find potential conflicts
            for rule in dependencies:
                if rule.dependency_type == DependencyType.CONFLICTS:
                    analysis["potential_conflicts"].append(rule.to_dict())
            
            # Determine risk level
            total_impact = (
                len(analysis["direct_dependencies"]) +
                len(analysis["direct_dependents"]) +
                len(analysis["transitive_dependencies"]) +
                len(analysis["transitive_dependents"]) +
                len(analysis["potential_conflicts"])
            )
            
            if total_impact > 10:
                analysis["risk_level"] = "high"
            elif total_impact > 5:
                analysis["risk_level"] = "medium"
            else:
                analysis["risk_level"] = "low"
            
            return analysis
            
        except Exception as e:
            error_response = handle_error(e, {
                "operation": "get_impact_analysis",
                "flag_name": flag_name
            })
            raise DependencyError(f"Failed to get impact analysis: {error_response['message']}")
    
    async def get_resolution_stats(self) -> Dict[str, Any]:
        """Get dependency resolution statistics."""
        return self.resolution_stats.copy()
    
    async def list_dependency_rules(self, 
                                  flag_name: str = None,
                                  dependency_type: DependencyType = None) -> List[DependencyRule]:
        """List dependency rules with optional filtering."""
        rules = []
        
        for flag_rules in self.dependency_graph.edges.values():
            for rule in flag_rules:
                if flag_name and rule.source_flag != flag_name and rule.target_flag != flag_name:
                    continue
                if dependency_type and rule.dependency_type != dependency_type:
                    continue
                rules.append(rule)
        
        return sorted(rules, key=lambda r: (r.priority, r.source_flag, r.target_flag), reverse=True)